Volley网络库源码分析

综述

volley有三个层次的线程,分别为main thread、cache thread 以及network thrads。RequestQueue会维护一个缓存调度线程和一个网络调度线程,当一个Request被加入到队列中时,cache线程会对其进行筛选,如果这个请求的内容可以在缓存中找到,cache线程会亲自解析相应内容,并分发到UI线程。如果缓存中没有,这个请求就会被加入到NetWorkQueue中,所有真正准备进行网络通信的Request都在这里,第一个可用的net线程会从Queue中取出一个请求发送给服务器。当响应数据到的时候,这个net线程会解析原始响应数据并缓存,随后将解析的结果发送给主线程。

基本使用

首先在使用中,我们会在应用中创建一个请求队列

1
mRequestQueue = Volley.newRequestQueue(this);

newRequestQueue的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//volley.java
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    String userAgent = "volley/0";
    try {
           String packageName = context.getPackageName();
           PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
           userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {}
    if (stack == null) {
       if (Build.VERSION.SDK_INT >= 9) {
           stack = new HurlStack();
      } else {    
           stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
      }
    }
    Network network = new BasicNetwork(stack);
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();
    return queue;
}

在创建请求队列中,我们还会创建一个BasicNetWork 这是volley使用的网络请求库,通过一个httpstack 对象来初始化,HttpStack是一个网络请求的接口。可以看到,在SDK版本小于9时默认使用的是HttpClient ,否则就用HttpUrlConnection当做其请求库。随后创建完queue后就启动该请求队列。在看start之前我们先看看RequestQueue类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class RequestQueue {
   /** Used for generating monotonically-increasing sequence numbers for requests. */
   private AtomicInteger mSequenceGenerator = new AtomicInteger();
   /**
    * Staging area for requests that already have a duplicate request in flight.
    *
    * <ul>
    *     <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache
    *         key.</li>
    *     <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request
    *         is <em>not</em> contained in that list. Is null if no requests are staged.</li>
    * </ul>
    */
   private final Map<String, Queue<Request>> mWaitingRequests =
           new HashMap<String, Queue<Request>>();
   /**
    * The set of all requests currently being processed by this RequestQueue. A Request
    * will be in this set if it is waiting in any queue or currently being processed by
    * any dispatcher.
    */
   private final Set<Request> mCurrentRequests = new HashSet<Request>();//请求队列
   /** The cache triage queue. */
   private final PriorityBlockingQueue<Request> mCacheQueue =
       new PriorityBlockingQueue<Request>(); //缓存阻塞队列
   /** The queue of requests that are actually going out to the network. */
   private final PriorityBlockingQueue<Request> mNetworkQueue =
       new PriorityBlockingQueue<Request>(); //网络请求阻塞队列
   /** Number of network request dispatcher threads to start. */
   private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; //默认线程池大小
   /** Cache interface for retrieving and storing respones. */
   private final Cache mCache; //使用的缓存对象
   /** Network interface for performing requests. */
   private final Network mNetwork; //使用的网络请求对象
   /** Response delivery mechanism. */
   private final ResponseDelivery mDelivery;//转发器
   /** The network dispatchers. */
   private NetworkDispatcher[] mDispatchers; //网络调度线程
   /** The cache dispatcher. */
   private CacheDispatcher mCacheDispatcher; //缓存调度线程
   ...
}

RequestQueue包含了多个队列和调度器以及一个ResponseDelivery转发器,这里NetworkDispatcher和CacheDispatcher都是继承自Thread类。

构造方法

1
2
3
4
5
6
7
8
9
10
11
12
public RequestQueue(Cache cache, Network network, int threadPoolSize,
           ResponseDelivery delivery) {
       mCache = cache;
       mNetwork = network;
       mDispatchers = new NetworkDispatcher[threadPoolSize];
       mDelivery = delivery;
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
       this(cache, network, threadPoolSize,
               new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

再来看start方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   //RequestQueue.java
public void start() {
    stop();  // Make sure any currently running dispatchers are stopped.
       // Create the cache dispatcher and start it.
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();
       // Create network dispatchers (and corresponding threads) up to the pool size.
    for (int i = 0; i < mDispatchers.length; i++) {
       NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                   mCache, mDelivery);
       mDispatchers[i] = networkDispatcher;
       networkDispatcher.start();
    }
}

在start中,创建了缓存调度线程(一个)和网络调度线程(四个),并启动它们。也就是说,在我们创建了请求队列后,就有5个线程在后台运行,不断等待网络请求的到来。我们创建好请求队列后,我们一般会add到该队列,所以看下add的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public Request add(Request request) {
       // Tag the request as belonging to this queue and add it to the set of current requests.
       request.setRequestQueue(this);//设置请求所属队列
       synchronized (mCurrentRequests) {
           mCurrentRequests.add(request);//添加该请求
      }
       // Process requests in the order they are added.
       request.setSequence(getSequenceNumber());//得到该请求序列
       request.addMarker("add-to-queue");
       // If the request is uncacheable, skip the cache queue and go straight to the network.
       if (!request.shouldCache()) { //判断是否可缓存
           mNetworkQueue.add(request);//不可缓存的话就直接添加到网络请求队列中
           return request;
      }
       // Insert request into stage if there's already a request with the same cache key in flight.
       synchronized (mWaitingRequests) {
           String cacheKey = request.getCacheKey();//得到请求的key,实际上就是请求的url
           if (mWaitingRequests.containsKey(cacheKey)) { //等待请求map表中已经存在该请求了
               // There is already a request in flight. Queue up.
               Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);//获取该请求对应的等待队列
               if (stagedRequests == null) {
                   stagedRequests = new LinkedList<Request>();
              }
               stagedRequests.add(request); //添加请求进来
               mWaitingRequests.put(cacheKey, stagedRequests);//重置请求的map
               if (VolleyLog.DEBUG) {
                   VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
              }
          } else { //请求目前还未添加进缓存队列
               // Insert 'null' queue for this cacheKey, indicating there is now a request in
               // flight.
               mWaitingRequests.put(cacheKey, null);
               mCacheQueue.add(request); //添加到缓存队列
          }
           return request;
      }
}

这里主要针对需要缓存的request说下,volley默认的request是需要缓存的,在需要缓存时,主要针对mWaitingRequests这个map表进行操作。这map表主要用来记录对应每个请求目前的请求队列,因为volley在网络出现问题后会对再次request进行重试,而重试的次数是可以设置的,重试一旦超过设置值就会超时。这里用mWaitingRequests就是来记录重试队列的。在应用层添加完request后,缓存调度线程就可以处理这个request了,所以看看CacheDispatcher的run方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//CacheDispatcher 
@Override
public void run() {
   if (DEBUG) VolleyLog.v("start new dispatcher");
   Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
   // Make a blocking call to initialize the cache.
   mCache.initialize(); //缓存初始化
   while (true) {
       try {
           // Get a request from the cache triage queue, blocking until
           // at least one is available.
           final Request request = mCacheQueue.take(); //取一个请求出来
           request.addMarker("cache-queue-take");
           // If the request has been canceled, don't bother dispatching it.
           if (request.isCanceled()) { //如果被取消了就continue
               request.finish("cache-discard-canceled");
               continue;
           }
           // Attempt to retrieve this item from cache.
           Cache.Entry entry = mCache.get(request.getCacheKey()); //取该request对应的缓存
           if (entry == null) {
             request.addMarker("cache-miss");
              // Cache miss; send off to the network dispatcher.
              mNetworkQueue.put(request); //没有缓存就直接添加到网络请求队列中,然后continue
                   continue;
           }
           // If it is completely expired, just send it to the network.
           if (entry.isExpired()) { //缓存是否过期
                   request.addMarker("cache-hit-expired");
                   request.setCacheEntry(entry);
                   mNetworkQueue.put(request); //如果过期,同样需要添加到网络请求队列中
                   continue;
           }
               // We have a cache hit; parse its data for delivery back to the request.
           request.addMarker("cache-hit");
           //这里就算缓冲命中了
           Response<?> response = request.parseNetworkResponse(
                       new NetworkResponse(entry.data, entry.responseHeaders));
           request.addMarker("cache-hit-parsed");
           if (!entry.refreshNeeded()) {
              // Completely unexpired cache hit. Just deliver the response.
              mDelivery.postResponse(request, response); //通过转发器递交响应给Main Thread
          } else { //缓存命中了,但是需要刷新的情况
                   // Soft-expired cache hit. We can deliver the cached response,
                   // but we need to also send the request to the network for
                   // refreshing.
                   request.addMarker("cache-hit-refresh-needed");
                   request.setCacheEntry(entry);
                   // Mark the response as intermediate.
                   response.intermediate = true;
                   // Post the intermediate response back to the user and have
                   // the delivery then forward the request along to the network.
                   //先递交响应给main thread 随后在将该请求添加到网络请求队列以刷新缓存
                   mDelivery.postResponse(request, response, new Runnable() {
                       @Override
                       public void run() {
                           try {
                               mNetworkQueue.put(request);
                          } catch (InterruptedException e) {
                               // Not much we can do about this.
                          }
                      }
                  });
              }
          } catch (InterruptedException e) {
               // We may have been interrupted because it was time to quit.
               if (mQuit) {
                   return;
              }
               continue;
          }
      }
  }

总体来说CacheDispatcher的处理流程很简单 ,主要的内容在注释我做了说明。在取到一个request后,需要该请求是否存在缓存,该缓存是否过期,是否需要刷新等问题。 接下来在看看NetworkDispatcher这个网络调度线程,它的任务就是从网络请求队列中取出request进行网络请求了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
   @Override
   public void run() {
       Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
       Request request;
       while (true) {
           try {
               // Take a request from the queue.
               request = mQueue.take(); //取出一个请求
          } catch (InterruptedException e) {
               // We may have been interrupted because it was time to quit.
               if (mQuit) {
                   return;
              }
               continue;
          }
           try {
               request.addMarker("network-queue-take");
               // If the request was cancelled already, do not perform the
               // network request.
               if (request.isCanceled()) { //如果被取消了continue
                   request.finish("network-discard-cancelled");
                   continue;
              }
               // Tag the request (if API >= 14)
               if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                   TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
              }
               // Perform the network request.
               //通过网络请求库向服务器请求数据,响应保存在networkResponse中
               NetworkResponse networkResponse = mNetwork.performRequest(request);
               request.addMarker("network-http-complete");
               // If the server returned 304 AND we delivered a response already,
               // we're done -- don't deliver a second identical response.
               //响应请求的内容为被修改,服务器会返回304的响应码
               if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                   request.finish("not-modified");
                   continue;
              }
               // Parse the response here on the worker thread.
               //对相应进行解析,这里调用的parseNetworkResponse在request子类中实现
               Response<?> response = request.parseNetworkResponse(networkResponse);
               request.addMarker("network-parse-complete");
               // Write to cache if applicable.
               // TODO: Only update cache metadata instead of entire record for 304s.
               if (request.shouldCache() && response.cacheEntry != null) { //需要缓存
                   mCache.put(request.getCacheKey(), response.cacheEntry);
                   request.addMarker("network-cache-written");
              }
               // Post the response back.
               request.markDelivered();
               mDelivery.postResponse(request, response); //转发器投递响应到主线程
          } catch (VolleyError volleyError) {
               parseAndDeliverNetworkError(request, volleyError);
          } catch (Exception e) {
               VolleyLog.e(e, "Unhandled exception %s", e.toString());
               mDelivery.postError(request, new VolleyError(e));
          }
      }
  }

可以看到网络请求线程的处理更加简单,主要就是从网络请求队列中取出reqeust进行网络请求,并根据设置将响应保存在缓存中,最后转发响应到主线程中。 我们看到在缓存调度线程和网络调度线程中都使用到了ResponseDelivery对象将结果投递到主线程,这里我们看看这个Delivery 。事实上,ResponseDelivery只是一个接口,我们在创建RequestQueue时使用的ExecutorDelivery它实现了该接口。

1
2
3
4
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
       this(cache, network, threadPoolSize,
               new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

这里使用通过一个Handler来构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ExecutorDelivery(final Handler handler) {
   // Make an Executor that just wraps the handler.
   mResponsePoster = new Executor() {
       @Override
       public void execute(Runnable command) {
           handler.post(command);
      }
  };
}

@Override
public void postResponse(Request<?> request, Response<?> response) {
   postResponse(request, response, null);
}

@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
   request.markDelivered();
  request.addMarker("post-response");
   mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

Dlivery的有两个重载的PostResponse,在缓存调度线程和网络调度线程都使用了两个参数的,三个参数的用在了缓存需要刷新时的情况,这时我们不仅要将结果投递给主线程而且要刷新缓存(其实只是将请求添加到了网络请求队列中),刷新缓存的操作就是通过第三个参数Runable实现的。 我们看看这个ResponseDeliveryRunnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private class ResponseDeliveryRunnable implements Runnable {
       private final Request mRequest;
       private final Response mResponse;
       private final Runnable mRunnable;
       public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
           mRequest = request;
           mResponse = response;
           mRunnable = runnable;
      }
       @SuppressWarnings("unchecked")
       @Override
       public void run() {
           // If this request has canceled, finish it and don't deliver.
           if (mRequest.isCanceled()) {
               mRequest.finish("canceled-at-delivery");
               return;
          }
           // Deliver a normal response or error, depending.
           if (mResponse.isSuccess()) {
               mRequest.deliverResponse(mResponse.result); //同样调用两个参数的将响应投递给主线程
          } else {
               mRequest.deliverError(mResponse.error);
          }
           // If this is an intermediate response, add a marker, otherwise we're done
           // and the request can be finished.
           if (mResponse.intermediate) {
               mRequest.addMarker("intermediate-response");
          } else {
               mRequest.finish("done");
          }
           // If we have been provided a post-delivery runnable, run it.
           if (mRunnable != null) {
               mRunnable.run(); //调用run方法
          }
      }
}

下面我们看看Request类,这个请求类在我们的主线程中使用,是个抽象类,其子类需要实现

1
2
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
abstract protected void deliverResponse(T response);

分别用来解析响应和转发响应

在Request中,有个RetryPolicy成员通过DefaultRetryPolicy构造,这是个重试策略,主要在用在请求需要重新请求时,我们看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	 
public class DefaultRetryPolicy implements RetryPolicy {
   /** The current timeout in milliseconds. */
   private int mCurrentTimeoutMs;
   /** The current retry count. */
   private int mCurrentRetryCount; //当前重试次数
   /** The maximum number of attempts. */
   private final int mMaxNumRetries; //最大重试次数
   /** The backoff multiplier for for the policy. */
   private final float mBackoffMultiplier; //备值因子
   /** The default socket timeout in milliseconds */
   public static final int DEFAULT_TIMEOUT_MS = 2500;
   /** The default number of retries */
   public static final int DEFAULT_MAX_RETRIES = 1;
   /** The default backoff multiplier */
   public static final float DEFAULT_BACKOFF_MULT = 1f;
   /**
    * Constructs a new retry policy using the default timeouts.
    */
   public DefaultRetryPolicy() {
       this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
  }
   /**
    * Constructs a new retry policy.
    * @param initialTimeoutMs The initial timeout for the policy.
    * @param maxNumRetries The maximum number of retries.
    * @param backoffMultiplier Backoff multiplier for the policy.
    */
   public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
       mCurrentTimeoutMs = initialTimeoutMs;
       mMaxNumRetries = maxNumRetries;
       mBackoffMultiplier = backoffMultiplier;
  }
   ....
   /**
    * Prepares for the next retry by applying a backoff to the timeout.
    * @param error The error code of the last attempt.
    */
   @Override
   public void retry(VolleyError error) throws VolleyError {
       mCurrentRetryCount++;  //重试次数加1
       mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);//计算当前超时时间
       if (!hasAttemptRemaining()) {
           throw error;
      }
  }
   /**
    * Returns true if this policy has attempts remaining, false otherwise.
    */
   protected boolean hasAttemptRemaining() {
       return mCurrentRetryCount <= mMaxNumRetries;
  }
}

最后我们看看这个RetryPolicy的使用,它主要用在了网络请求类BasicNetWork中,主要看看接口方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
   long requestStart = SystemClock.elapsedRealtime();
   while (true) {
       HttpResponse httpResponse = null;
       byte[] responseContents = null;
       Map<String, String> responseHeaders = new HashMap<String, String>();
       try {
           // Gather headers.
           Map<String, String> headers = new HashMap<String, String>();
           addCacheHeaders(headers, request.getCacheEntry());
           httpResponse = mHttpStack.performRequest(request, headers); //请求数据,获取响应
           StatusLine statusLine = httpResponse.getStatusLine();
           int statusCode = statusLine.getStatusCode(); //获取状态码
           responseHeaders = convertHeaders(httpResponse.getAllHeaders());
           // Handle cache validation.
           if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
               return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                       request.getCacheEntry().data, responseHeaders, true);
          }
           // Some responses such as 204s do not have content. We must check.
           if (httpResponse.getEntity() != null) {
             responseContents = entityToBytes(httpResponse.getEntity());
          } else {
             // Add 0 byte response as a way of honestly representing a
              // no-content request.
             responseContents = new byte[0];
          }
           // if the request is slow, log it.
           long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
           logSlowRequests(requestLifetime, request, responseContents, statusLine);
           if (statusCode < 200 || statusCode > 299) {
               throw new IOException();
          }
           return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
      } catch (SocketTimeoutException e) {//socket 超时
           attemptRetryOnException("socket", request, new TimeoutError());
      } catch (ConnectTimeoutException e) { //连接超时
           attemptRetryOnException("connection", request, new TimeoutError());
      } catch (MalformedURLException e) {
           throw new RuntimeException("Bad URL " + request.getUrl(), e);
      } catch (IOException e) {
           int statusCode = 0;
           NetworkResponse networkResponse = null;
           if (httpResponse != null) {
               statusCode = httpResponse.getStatusLine().getStatusCode();
          } else {
               throw new NoConnectionError(e);
          }
           VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
           if (responseContents != null) {
               networkResponse = new NetworkResponse(statusCode, responseContents,
                       responseHeaders, false);
               if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                       statusCode == HttpStatus.SC_FORBIDDEN) {//未认证异常
                   attemptRetryOnException("auth",
                           request, new AuthFailureError(networkResponse));
              } else {
                    // TODO: Only throw ServerError for 5xx status codes.
                   throw new ServerError(networkResponse);
              }
          } else {
               throw new NetworkError(networkResponse);
          }
       }
   }
}

我们看到performRequest是通过一个while循环来实现的,这是为了在发生请求出错是再次进行重试。在request超时、或者认证失败后都会调用attemptRetryOnException,这里就会进行重试。

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void attemptRetryOnException(String logPrefix, Request<?> request,
           VolleyError exception) throws VolleyError {
  RetryPolicy retryPolicy = request.getRetryPolicy();//获取重试策略
   int oldTimeout = request.getTimeoutMs(); //获取超时值
  try {
       retryPolicy.retry(exception);//重试
  } catch (VolleyError e) {
       request.addMarker(
               String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
       throw e;
  }
   request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}

注意这里退出重试循环是通过retry抛出VolleyError异常来结束的

volley的缓存策略

volley的缓存是通过服务端进行控制的,这样的方式比本地存储更加灵活,同服务端进行交互缓存能降低通信量,同时减轻本地缓存的压力。比如之前我们看到了服务端如何通过304的响应码通知内容未修改,这时就不需要再进行请求了。 volley的缓存类是在创建RequestQueue时指定的,这个缓存类以流的方式实现了Cach Entry的存取。所以我们看看这个Entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static class Entry {
       /** The data returned from cache. */
       public byte[] data; //缓存的字节流
       /** ETag for cache coherency. */
       public String etag;
       /** Date of this response as reported by the server. */
       public long serverDate;
       /** TTL for this record. */
       public long ttl;
       /** Soft TTL for this record. */
       public long softTtl;
       /** Immutable response headers as received from server; must be non-null. */
       public Map<String, String> responseHeaders = Collections.emptyMap();
       /** True if the entry is expired. */
       public boolean isExpired() {
           return this.ttl < System.currentTimeMillis();
      }
       /** True if a refresh is needed from the original data source. */
       public boolean refreshNeeded() {
           return this.softTtl < System.currentTimeMillis();
      }
}

需要说明的是,这个Entry对应我们一个请求的缓存条目,这个是在Request子类根据响应解析出来的。解析类为HttpHeaderParser,当然我们也可以根据需求自己定义类来解析响应得到想要的缓存。下面我们看看这个解析的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//HttpHeaderParser.java
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
       long now = System.currentTimeMillis();
       Map<String, String> headers = response.headers;
       long serverDate = 0;
       long serverExpires = 0;
       long softExpire = 0;
       long maxAge = 0;
       boolean hasCacheControl = false;
       String serverEtag = null;
       String headerValue;
       headerValue = headers.get("Date"); //得到date
       if (headerValue != null) {
           serverDate = parseDateAsEpoch(headerValue);
      }
       headerValue = headers.get("Cache-Control"); //获取Cache-Control值,服务端需要设置这个值来标记缓存
       if (headerValue != null) {
           hasCacheControl = true;
           String[] tokens = headerValue.split(",");
           for (int i = 0; i < tokens.length; i++) {
               String token = tokens[i].trim();
               if (token.equals("no-cache") || token.equals("no-store")) { //对应的值no-cache、no-store
                   return null;
              } else if (token.startsWith("max-age=")) { //对应的值为max-age,这个值标记了缓存的生存时间
                   try {
                       maxAge = Long.parseLong(token.substring(8));
                  } catch (Exception e) {
                  }
              } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                   maxAge = 0;
              }
          }
      }
       headerValue = headers.get("Expires"); //过期时间
       if (headerValue != null) {
           serverExpires = parseDateAsEpoch(headerValue);
      }
       serverEtag = headers.get("ETag");
       // Cache-Control takes precedence over an Expires header, even if both exist and Expires
       // is more restrictive.
       if (hasCacheControl) {
           softExpire = now + maxAge * 1000;//计算过期时间 这里以毫秒为单位
      } else if (serverDate > 0 && serverExpires >= serverDate) {
           // Default semantic for Expire header in HTTP specification is softExpire.
           softExpire = now + (serverExpires - serverDate);//通过过期日期来计算过期时间
      }
       Cache.Entry entry = new Cache.Entry();
       entry.data = response.data;
       entry.etag = serverEtag;
       entry.softTtl = softExpire;
       entry.ttl = entry.softTtl;//其实就是我们的过期时间
       entry.serverDate = serverDate;
       entry.responseHeaders = headers;
       return entry;
  }

我们在缓存调度线程中判断缓存条目失效的方法为

1
2
3
public boolean isExpired() {
   return this.ttl < System.currentTimeMillis();
}

即当前ttl的值小于当前系统时间就算过期了。所以我们需要服务端设置头部Cache-Control的max-age或者expires来控制缓存的生命时间已达到本地缓存的目的。

总结

volley使用场景 数据量不大但通信频繁的场景

相关链接

Volley主页

坚持原创技术分享,您的支持将鼓励我继续创作!